package org.fujene.diskblock;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.fujene.memoryblock.MemoryBlock;
import org.fujene.structure.Constants;
import org.fujene.toolbox.ByteConverter;

public abstract class DiskBlock{
	// 定数はココに追加
	protected static final int HEADERLEN = 8;

	protected static final int PTR = Constants.BYTE_GLOBALPTR;
	protected static final int RELPTR = Constants.BYTE_RELATIVEPTR;
	protected static final int STRLEN = Constants.BYTE_STRLEN;
	protected static final int DOCFREQ = Constants.BYTE_DOCFREQ;
	protected static final int TERMFREQ = Constants.BYTE_TERMFREQ;
	protected static final int IDDIFF = Constants.BYTE_INVINDEXDIFF;

	private int ID;
	private int type;
	protected int optionSize;
	protected int bodySize;
	protected int bodyVacancy;
	protected byte version;
	protected File fileDesc;
	private MemoryBlock memBlock;

	// メモリブロックから生成
	public DiskBlock(MemoryBlock memBlock, String dir){
		this.version = Constants.FINALVER;

		this.ID = memBlock.getID();
		this.type = memBlock.getType();
		this.optionSize = memBlock.getOptionSize();
		this.bodySize = memBlock.getBodySize();
		this.bodyVacancy = memBlock.getBodyVacancy();
		this.memBlock = memBlock;

		this.fileDesc = new File(dir + memBlock.getID() + ".blk");
	}

	public DiskBlock(File fileDesc){
		this.version = Constants.FINALVER;
		this.fileDesc = fileDesc;

		int varidate = readRequiredHeaders();

		if(varidate != 0){
			return;
		}
	}

	public DiskBlock(int ID, File fileDesc, int type, int defaultBlockSize, int defaultOptionSize){
		this.version = Constants.FINALVER;

		this.fileDesc = fileDesc;

		// デフォルト値を入れておく
		this.ID = ID;
		this.type = type;
		this.bodySize = defaultBlockSize;
		this.optionSize = defaultOptionSize;
	}

	// 泥臭いこと
	private int readRequiredHeaders(FileInputStream stream) throws IOException{
		byte[] tempb = new byte[4];

		stream.read(tempb);
		if(!verifyVersion(tempb))
			return -2;

		stream.read(tempb);
		this.ID = ByteConverter.byteToInt(tempb);

		stream.read(tempb);
		this.type = ByteConverter.byteToInt(tempb);

		stream.read(tempb);
		this.optionSize = ByteConverter.byteToInt(tempb);

		stream.read(tempb);
		this.bodySize = ByteConverter.byteToInt(tempb);

		stream.read(tempb);
		this.bodyVacancy = ByteConverter.byteToInt(tempb);

		return 0;
	}

	public int getID(){
		return ID;
	}

	public int getType(){
		return type;
	}

	public String getTypeStr(){
		switch (this.type){
		case Constants.MEMORY_TYPE_DICT:
			return "Dictionary";
		case Constants.MEMORY_TYPE_INVINDEX:
			return "Invert Index";
		case Constants.MEMORY_TYPE_CONTENTS:
			return "Contents";
		case Constants.MEMORY_TYPE_SKIPPTRCONT:
			return "Skip Pointer for Content";
		case Constants.MEMORY_TYPE_SKIPPTR:
			return "Skip Pointer";
		case Constants.MEMORY_TYPE_PURECONTENT:
			return "Supplements";
		default:
			break;
		}

		return "Unknown";
	}

	protected int readRequiredHeaders(){
		FileInputStream stream = null;
		int retval = -3;
		try{
			stream = new FileInputStream(fileDesc);
			retval = readRequiredHeaders(stream);
		}catch (FileNotFoundException e){}catch (IOException e){}finally{
			try{
				if(stream != null)
					stream.close();
			}catch (IOException e){}
		}

		return retval;
	}

	private boolean verifyVersion(byte[] head){
		// 現バージョン: "far"22
		if(head[0] != 'f')
			return false;
		if(head[1] != 'a')
			return false;
		if(head[2] != 'r')
			return false;
		if(head[3] < Constants.FINALVER)
			return false;
		else{
			this.version = head[3];
		}

		return true;
	}

	// 読み出ししかしないのでこれでよい
	protected int getOption(int index){
		if(index >= optionSize)
			return 0;

		FileInputStream stream = null;
		int retval = -3;
		byte[] b = new byte[4];
		try{
			stream = new FileInputStream(fileDesc);
			switch (version){
			case Constants.FINALVER: // 0.32
				stream.skip((4 * HEADERLEN) + (4 * index));
				stream.read(b);
				retval = ByteConverter.byteToInt(b);
				break;

			default:
				break;
			}
		}catch (FileNotFoundException e){
		}catch (IOException e){
		}finally{
			try{
				if(stream != null)
					stream.close();
			}catch (IOException e){
			}
		}

		return retval;
	}

	protected byte[] getBody(int from, int length){
		if(from + length > bodySize)
			return null;

		FileInputStream stream = null;
		byte[] val = new byte[length];
		try{
			stream = new FileInputStream(fileDesc);
			switch (version){
			case Constants.FINALVER: // 0.22
				stream.skip((4 * HEADERLEN) + (4 * optionSize) + from);
				stream.read(val);
				break;

			default:
				break;
			}
		}catch (FileNotFoundException e){
		}catch (IOException e){
		}finally{
			try{
			if(stream != null)
					stream.close();
				}catch (IOException e){
				}
		}

		return val;
	}

	public abstract void readToMemory(MemoryBlock memBlock);

	public abstract byte[] getData();

	// メモリブロックを書き込み
	public void save(){
		if(memBlock != null){
			FileOutputStream os = null;

			try{
				os = new FileOutputStream(fileDesc);

				// ヘッダ部
				os.write(new byte[]{'f', 'a', 'r', version});
				os.write(ByteConverter.intToByte(this.ID, 4));
				os.write(ByteConverter.intToByte(this.type, 4));
				os.write(ByteConverter.intToByte(this.optionSize, 4));
				os.write(ByteConverter.intToByte(this.bodySize, 4));
				os.write(ByteConverter.intToByte(this.bodyVacancy, 4));
				os.write(ByteConverter.intToByte(0, 4));
				os.write(ByteConverter.intToByte(0, 4));

				// オプション
				os.write(this.memBlock.getOptionByteStream());

				// 本体
				os.write(this.memBlock.getBody(0, this.bodySize));
			}catch (FileNotFoundException e){
				System.err.println("ERROR: File cannot open.");
			}catch (IOException e){
				e.printStackTrace();
			}finally{
				if(os != null){
					try{
						os.close();
					}catch (IOException e){}
				}
			}
		}
	}

}